--
-- Ret/prot paladin rotation helper
--

if _G.Rotor.player_class ~= 'PALADIN' then return end

local _, ns = ...
local L = ns.L

-------------------------------------------
-- Local references to globals for speed and lint's happiness
-------------------------------------------
local Rotor = _G.Rotor

local strsplit = _G.strsplit
local max = _G.max
local min = _G.min
local GetTime = _G.GetTime
local UnitBuff = _G.UnitBuff
local IsSpellKnown = _G.IsSpellKnown
local UnitHealthMax = _G.UnitHealthMax
local UnitHealth = _G.UnitHealth
local UnitCreatureType = _G.UnitCreatureType
local UnitPowerMax = _G.UnitPowerMax

----------------------------------------
-- Localization
----------------------------------------
if _G.GetLocale() == "ruRU" then
    L.Demon = 'Демон'
    L.Undead = 'Нежить'
end

-------------------------------------------
-- Constants
-------------------------------------------

--
-- Spell identifiers
--
local JUDG_ID   = 20271     -- judgement
local TV_ID     = 85256     -- templar's verdict
local CS_ID     = 35395     -- crusader strike
local EXO_ID    = 879       -- exorcism
local BoK_ID    = 20217     -- blessing of the kings
local BoM_ID    = 19740     -- blessing of the might
local AS_ID     = 31935     -- avenger's shield
local SotR_ID   = 53600     -- shield of the righteous
local AoW_ID    = 53486     -- the art of war
local HoW_ID    = 24275     -- hammer of wrath
local INQ_ID    = 84963     -- Inquisition
local HotR_ID   = 53595     -- hammer of the righteous
local ZEAL_ID   = 85696     -- zealotry
local WoG_ID    = 85673     -- word of glory
local REBUKE_ID = 96231     -- rebuke
local AW_ID     = 31884     -- avenging wrath
local AD_ID     = 31850     -- ardent defender
local GC_ID     = 85416     -- grand crusader
local DP_ID     = 498       -- divine protection
local DPURP_ID  = 86172     -- divine purpose
local SoR_ID    = 20154     -- seal of the righteous
local SoI_ID    = 20165     -- self of the insight
local SoJ_ID    = 20164     -- seal of the judgement
local SoT_ID    = 31801     -- seal of the truth
local DEV_AURA_ID = 465     -- devotion aura
local RET_AURA_ID = 7294    -- retribution aura
local CONC_AURA_ID = 19746  -- concentration aura
local RES_AURA_ID = 19891   -- resistance aura
local HW_ID     = 2812      -- holy wrath
local CONS_ID   = 26573     -- consecration
local GotAK_ID  = 86150     -- Guardion oh the ancient kings
local DG_ID     = 70940     -- divine guaridan

local CS_NAME = Rotor.spell_name_cache[CS_ID]
local HotR_NAME = Rotor.spell_name_cache[HotR_ID]
local EXO_NAME = Rotor.spell_name_cache[EXO_ID]
local WoG_NAME = Rotor.spell_name_cache[WoG_ID]
local JUDG_NAME = Rotor.spell_name_cache[JUDG_ID]

-- seal names
local SEAL_NAMES = {
    Rotor.spell_name_cache[SoT_ID],
    Rotor.spell_name_cache[SoI_ID],
    Rotor.spell_name_cache[SoR_ID],
    Rotor.spell_name_cache[SoJ_ID],
}

-- buff names
local BoK_NAME = Rotor.spell_name_cache[BoK_ID]
local BoM_NAME = Rotor.spell_name_cache[BoM_ID]
local MotW_NAME = Rotor.spell_name_cache[1126] -- mark of the wild

-- aura names
local COMBAT_AURA_NAMES = {
    Rotor.spell_name_cache[DEV_AURA_ID],
    Rotor.spell_name_cache[RET_AURA_ID],
    Rotor.spell_name_cache[CONC_AURA_ID],
    Rotor.spell_name_cache[RES_AURA_ID]
}

-- Spell/aura defs
local spellAndAuraDefs = {
    all_specs = {
        spells = {
            judg = JUDG_ID,
            cs = CS_ID,
            exo = EXO_ID,
            how = HoW_ID,
            inq = {INQ_ID, with_buff = true},
            rebuke = {REBUKE_ID, icon_pos = 11, off_gcd = true, range_check = true},
            aw = {AW_ID, icon_pos = 6, with_buff = true},
            hw = HW_ID,
            cons = CONS_ID,
        },
    },
    protection = {
        spec = 2,
        spells = {
            as = AS_ID,
            sotr = SotR_ID,
            {AD_ID, icon_pos = 7, with_buff = true}, -- ardent defender
            {DG_ID, icon_pos = 9}, -- divine guardian
            {DP_ID, icon_pos = 10, with_buff = true}, -- divine protection
            {GotAK_ID, icon_pos = 8, with_buff = true},
        },
    },
    retribution = {
        spec = 3,
        spells = {
            tv = TV_ID,
            zeal = {ZEAL_ID, icon_pos = 7, icon_type = 'reminder', with_buff = true},
            {GotAK_ID, icon_pos = 8, with_buff = true},
        },
        auras = {
            aow = AoW_ID,
            dp = DPURP_ID,
        },
    },
}

-- The module
local Mod = Rotor:NewModule('Paladin')

-- variables

local IsRet = false
local IsProt = false
local HaveRet2pT13 = false
local HaveProt2pT13 = false
local bom_known = false
local bok_known = false

local spells
local buffs
local debuffs

local cons_cost = 0

-- Local vars
local now = 0
local gcd = 0
local hp_delta = 0
local dp_consumed = false
local aow_consumed = false
local strong_target = false
local exo_vulnerable_target = false
local last_hp = -1

--
-- Methods implementation
--

local retT13ItemIDs = {
    -- Retribution
    -- chest
    78727,
    76874,
    78822,
    -- hands
    78675,
    76875,
    78770,
    -- shoulder
    78742,
    76878,
    78837,
    -- head
    78693,
    76876,
    78788,
    -- legs
    78712,
    76877,
    78807
}

local protT13ItemIDs = {
    --todo
}

local function Check2pT13()
    local slotItemIDs = {Rotor:GetSetItemSlotItems()}
    local numRetSetItems = 0
    local numProtSetItems = 0

    for _, id in pairs(slotItemIDs) do
        if ns.tcontains(retT13ItemIDs, id) then
            numRetSetItems = numRetSetItems + 1
        end
        if ns.tcontains(protT13ItemIDs, id) then
            numProtSetItems = numProtSetItems + 1
        end
    end
    HaveRet2pT13 = numRetSetItems > 1
    HaveProt2pT13 = numProtSetItems > 1
end
----------------------------------------
function Mod:Init(opts)
    IsProt = Rotor.spec == 2
    IsRet = Rotor.spec == 3

    spells, buffs, debuffs = Rotor:RegisterSpellsAndAuras(spellAndAuraDefs)

    if spells.zeal then
        spells.zeal.check_enabled = function() return (buffs.dp.time_left > 2) or (Rotor.combo_points == 3) end
    end

    Rotor:initBlIndicator(4)
    Rotor:showUpperText()
    Rotor:setInterruptSpell(spells.rebuke)

    bom_known = IsSpellKnown(BoM_ID)
    bok_known = IsSpellKnown(BoK_ID)

    cons_cost = Rotor:GetSpellCost(CONS_ID)

    Check2pT13()

    Rotor:printOptInfo('Paladin module initialized ('..ns.iif(IsRet, 'Ret', 'Prot')..')')

    return true
end
----------------------------------------
function Mod:Done()
    spells = nil
    buffs = nil
    debuffs = nil
end
----------------------------------------
function Mod:InventoryChanged()
    Check2pT13()
end
----------------------------------------
function Mod:CastSent(spell_name)
    local id = Rotor.spell_id_cache[spell_name]

    if (id == CS_ID) or (id == HotR_ID) or (id == JUDG_ID and HaveRet2pT13) then
        if (id == CS_ID) and buffs.zeal and buffs.zeal.active then
            hp_delta = 3
        else
            hp_delta = 1
        end
    elseif id == EXO_ID then
        aow_consumed = true
    elseif (id == WoG_ID) or (id == SotR_ID) or (id == TV_ID) or (id == INQ_ID) then
        if buffs.dp and buffs.dp.active then
            dp_consumed = true
        else
            hp_delta = -Rotor.combo_points
        end
    end
end
----------------------------------------
function Mod:CastFinished(spell_name)
    if (hp_delta ~= 0) then
        local id = Rotor.spell_id_cache[spell_name]

        if (id > 0) and
            ((id == CS_ID) or (id == HotR_ID) or (id == JUDG_ID and HaveRet2pT13)
            or (id == WoG_ID) or (id == SotR_ID) or (id == TV_ID) or (id == INQ_ID))
        then
            hp_delta = 0
        end
    end
end
----------------------------------------
local function CheckUnsent(spell, time_since_last_sent)
    return spell and (spell.sentAt <= spell.doneAt) and (spell.doneAt + (time_since_last_sent or 0.2) < now)
end
----------------------------------------
local function CheckOffCdAndUnsent(spell, time_since_last_sent, wth)
    return spell and (spell.cd - (wth or 0) <= gcd) and CheckUnsent(spell, time_since_last_sent)
end
----------------------------------------
local function AnySealUp()
    for _, seal_name in pairs(SEAL_NAMES) do
        if UnitBuff('player', seal_name) then
            return true
        end
    end
    return false
end
----------------------------------------
local function AnyCombatAuraUp()
    for _, aura_name in pairs(COMBAT_AURA_NAMES) do
        if UnitBuff('player', aura_name) then
            return true
        end
    end
    return false
end
----------------------------------------
function Mod:NextSkill(gcd_arg, cur_spell)
    gcd = gcd_arg + 0.1
    now = GetTime()

    if not AnySealUp() then
        return SoT_ID
    end

    if not Rotor.in_combat and not AnyCombatAuraUp() then
        if IsProt then
            return DEV_AURA_ID
        else
            return RET_AURA_ID
        end
    end

    if IsProt then
        return Mod:NextProtSkill(gcd_arg, cur_spell)
    else
        return Mod:NextRetSkill(gcd_arg, cur_spell)
    end
end
----------------------------------------
local function CheckHoW()
    return spells.how and spells.how.usable and CheckOffCdAndUnsent(spells.how)
end
----------------------------------------
local function CheckInq(gcd_arg)
    return
        strong_target
        and spells.inq
        and CheckUnsent(spells.inq)
        and (buffs.inq.time_left < 3)
        and ((Rotor.combo_points + hp_delta > 2) or (buffs.dp.time_left > gcd_arg))
end
----------------------------------------
function Mod:NextRetSkill(gcd_arg, cur_spell)
    if not Rotor.in_combat then
        if not UnitBuff('player', BoM_NAME) and not UnitBuff('player', BoK_NAME) then
            if bom_known then
                return BoM_ID
            elseif bok_known and not UnitBuff('player', BoK_NAME) and not UnitBuff('player', MotW_NAME) then
                return BoK_ID
            end
        end
    end

    local spell_id = 0
    local hp = min(Rotor.combo_points + hp_delta, 3)

    if (buffs.dp.time_left > gcd_arg) and not dp_consumed then
        hp = 3
    end

    if CheckInq(gcd_arg) then
        spell_id = INQ_ID
    else
        local can_use_tv = (hp == 3) and CheckUnsent(spells.tv)
        local cs_ready = CheckOffCdAndUnsent(spells.cs)
        local in_melee_range = Rotor:CheckInRange(CS_ID)
        local can_use_how = CheckHoW()
        local can_use_exo = (buffs.aow.time_left > gcd_arg) and not aow_consumed

        if not strong_target and can_use_how then
            spell_id = HoW_ID
        elseif can_use_tv and not buffs.dp.active and in_melee_range then
            -- 3HP TV
            spell_id = TV_ID
        elseif cs_ready and in_melee_range then
            spell_id = CS_ID
        elseif can_use_tv and in_melee_range then
            spell_id = TV_ID -- DP DV
        elseif exo_vulnerable_target and can_use_exo then
            spell_id = EXO_ID
        elseif can_use_how then
            spell_id = HoW_ID
        elseif can_use_exo then
            spell_id = EXO_ID
        elseif CheckOffCdAndUnsent(spells.judg) then
            spell_id = JUDG_ID
        elseif in_melee_range and CheckOffCdAndUnsent(spells.hw) then
            spell_id = HW_ID
        elseif strong_target and in_melee_range and CheckOffCdAndUnsent(spells.cons) and (Rotor.power > cons_cost * 1.5) then
            spell_id = CONS_ID
        end

        if (spell_id == 0) then
            if can_use_tv and not buffs.dp.active then
                -- 3HP TV
                spell_id = TV_ID
            elseif cs_ready then
                spell_id = CS_ID
            elseif can_use_tv then
                spell_id = TV_ID
            end
        end
    end

    return spell_id
end
----------------------------------------
function Mod:NextProtSkill(gcd_arg, cur_spell)
    local spell_id = 0
    local hp = min(Rotor.combo_points + hp_delta, 3)

    if not Rotor.in_combat then
        if not UnitBuff('player', BoK_NAME) and not UnitBuff('player', MotW_NAME) and not UnitBuff('player', BoM_NAME) then
            if bok_known then
                return BoK_ID
            elseif bom_known then
                return BoM_ID
            end
        end

        if CheckOffCdAndUnsent(spells.as) then
            return AS_ID
        elseif CheckOffCdAndUnsent(spells.judg) then
            return JUDG_ID
        else
            return EXO_ID
        end
    end

    local can_use_sotr = spells.sotr and (hp == 3) and CheckUnsent(spells.sotr)
    local cs_ready = CheckOffCdAndUnsent(spells.cs)
    local in_melee_range = Rotor:CheckInRange(CS_ID)

    if HaveProt2pT13 and CheckOffCdAndUnsent(spells.judg) then
        spell_id = JUDG_ID
    elseif can_use_sotr and in_melee_range then
        spell_id = SotR_ID
    elseif cs_ready and in_melee_range then
        spell_id = CS_ID
    elseif CheckOffCdAndUnsent(spells.as) then
        spell_id = AS_ID
    elseif CheckHoW() then
        spell_id = HoW_ID
    elseif CheckOffCdAndUnsent(spells.judg) then
        spell_id = JUDG_ID
    elseif CheckOffCdAndUnsent(spells.hw) and in_melee_range then
        spell_id = HW_ID
    elseif in_melee_range and CheckOffCdAndUnsent(spells.cons) and (Rotor.power > cons_cost * 1.5) then
        spell_id = CONS_ID
    end

    if spell_id == 0 then
        if can_use_sotr then
            spell_id = SotR_ID
        elseif cs_ready then
            spell_id = CS_ID
        end
    end

    return spell_id
end
----------------------------------------
function Mod:TargetChanged(old_target)
    hp_delta = 0
    dp_consumed = false
    aow_consumed = false
    if Rotor.on_target then
        strong_target = UnitHealth('target') > (UnitHealthMax('player') * 4)
        local uct = UnitCreatureType('target')
        exo_vulnerable_target = (uct == L.Demon) or (uct == L.Undead)
    end
end
----------------------------------------
function Mod:SpellsAurasAndResourcesUpdated()
    if buffs.dp and not buffs.dp.active then
        dp_consumed = false -- consume confirmed
    end

    if buffs.aow and not buffs.aow.active then
        aow_consumed = false
    end

    if Rotor.combo_points ~= last_hp then
        hp_delta = 0
        last_hp = Rotor.combo_points
    end
end

----------------------------------------
--
-- Command line handler
--
function Mod:HandleCommandLine(cmd)
    local cmd, args = strsplit(' ', (cmd or ''):lower(), 2)
    -- handle command line here
end

----------------------------------------
--
-- Module detection
--
function Mod:Detect()
    return (Rotor.spec == 3) or (Rotor.spec == 2)
end
